#include "../common/lm_error.h"
#include "../common/lm_mem.h"
#include "../common/lm_log.h"
#include "lm_parser.h"
#include "lm_string.h"
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include <unistd.h>
#include <dirent.h>


#define MAX_PER_LINE_LENGTH  1024
#define MAX_FILE_PATH        512
#define MAX_MACRO_NAME       512


static lm_macro_t *defconfig_menu_root = NULL;
static lm_macro_t *menu_root = NULL;
static lm_macro_t *macro_parent;


static lm_array_t src_list;
static lm_array_t obj_list;
static lm_array_t path_list;
static lm_array_t define_list;
static lm_array_t asm_list;
static lm_array_t lds_list;


static char *src_key_flag = "C_SOURCE";
static char *obj_key_flag = "C_OBJECT";
static char *path_key_flag = "C_INCLUDE";
static char *define_key_flag = "C_DEFINE";
static char *asm_key_flag = "ASM_SOURCE";
static char *lds_key_flag = "LDS_SOURCE";


static void lm_parser_error(const char *path, int line, char *info)
{
    printf("[ERROR] file: %s, lines: %d: %s\n", path, line, info);
}


static char* lm_parser_get_macro_value_from_menu(lm_macro_t *menu, lm_macro_t *macro)
{
    lm_macro_t *search = lm_macro_search_by_name(menu, macro->name);
    if (search != NULL) { // found macro name
        if (search->value == NULL) {
            return NULL;
        }
        else {
            return search->value;
        }
    }
    else {
        return NULL;
    }
}


static bool lm_parser_macro_operation_unsafe(char *express)
{
    int result = 0;
    char op = 0;

    for (int i = 0; i < strlen(express); i++) {
        if (express[i] == ' ') {
            continue;
        }
        else if (express[i] == '1' || express[i] == '0') {
            if (op == 0) {
                result = express[i] - '0';
            }
            else {
                switch (op) {
                case '&':
                    result &= (express[i] - '0');
                    break;
                case '|':
                    result |= (express[i] - '0');
                    break;
                }
                op = 0;
            }
        }
        else if (express[i] == '&' || express[i] == '|') {
            op = express[i];
        }
        else if (express[i] == '(' || express[i] == ')') {
            i++;
        }
    }

    return result;
}


static char *lm_parser_depend_preprocess(char *depend, bool flag)
{
    if(depend == NULL) {
        return NULL;
    }
    
    int dep_len = strlen(depend) + 1;

    char *output = lm_malloc(MAX_PER_LINE_LENGTH);
    if (output == NULL) {
        return NULL;
    }

    char *p = output;
    char macro_name[MAX_MACRO_NAME];
    char *name_p = macro_name;

    for (int i = 0; i < dep_len; i++) {
        if (*depend == ' ') {
            depend++;
            continue;
        }
        else if (*depend != '&' && *depend != '|' && *depend != '\0') {
            *name_p++ = *depend++;
        }
        else {
            *name_p++ = '\0';

            lm_macro_t *macro = NULL;
            if(!flag) {
                macro = lm_macro_search_by_name(defconfig_menu_root, macro_name);
            }
            
            if(macro == NULL) {
                macro = lm_macro_search_by_name(menu_root, macro_name);
            }
            if (macro != NULL) { // found macro name
                if (macro->value == NULL || strcmp(macro->value, "n") == 0 || strcmp(macro->value, " ") == 0) {
                    *p++ = '0';
                }
                else {
                    *p++ = '1';
                }
            }
            else {
                *p++ = '0';
            }

            *p++ = *depend++;
            name_p = macro_name;
        }
    }
    *p = '\0';

    return output;
}


int lm_parser_get_macro_depend_value(lm_macro_t *macro, bool flag)
{
    if (macro == NULL) {
        return -1;
    }

    char *dep = lm_parser_depend_preprocess(macro->depend, flag);
    if(dep == NULL) { //no dependence
        return 1;
    }

    return lm_parser_macro_operation_unsafe(dep);
}


static char *lm_parser_defconfig_get_macro_name(char *str)
{
    char *ret = str;
    char *p = str;
    while (*p) {
        if (*p == '=') {
            *p = '\0';
            return ret;
        }
        p++;
    }

    return NULL;
}


static char *lm_parser_defconfig_get_macro_value(char *str)
{
    while (*str) {
        if (*str == '=') {
            return str + 1;
        }
        str++;
    }

    return NULL;
}


static bool lm_is_skip_line(char *read_line)
{
    if (lm_str_find_char(read_line, '#') == 0) { // checked annotate
        return true;
    }
    else {
        if (lm_str_is_all_space(read_line)) {
            return true;
        }
    }
    return false;
}


static int lm_parser_defconfig_args_key(char *readline)
{
    char tmp[MAX_PER_LINE_LENGTH];
    strcpy(tmp, readline);

    int src_len = strlen(src_key_flag);
    tmp[src_len] = '\0';
    if(strcmp(tmp, src_key_flag) == 0) {
        return LM_OK;
    }

    strcpy(tmp, readline);
    int obj_len = strlen(obj_key_flag);
    tmp[obj_len] = '\0';
    if(strcmp(tmp, obj_key_flag) == 0) {
        return LM_OK;
    }

    strcpy(tmp, readline);
    int path_len = strlen(path_key_flag);
    tmp[path_len] = '\0';
    if(strcmp(tmp, path_key_flag) == 0) {
        return LM_OK;
    }

    strcpy(tmp, readline);
    int define_len = strlen(define_key_flag);
    tmp[define_len] = '\0';
    if(strcmp(tmp, define_key_flag) == 0) {
        return LM_OK;
    }

    strcpy(tmp, readline);
    int asm_len = strlen(asm_key_flag);
    tmp[asm_len] = '\0';
    if(strcmp(tmp, asm_key_flag) == 0) {
        return LM_OK;
    }

    strcpy(tmp, readline);
    int lds_len = strlen(lds_key_flag);
    tmp[lds_len] = '\0';
    if(strcmp(tmp, lds_key_flag) == 0) {
        return LM_OK;
    }

    return LM_ERR;
}


int lm_parser_defconfig_file(const char *path)
{
    if(path == NULL) {
        return 0; //no defconfig file
    }

    lm_macro_t *macro = NULL;
    defconfig_menu_root = lm_macro_add_menu(NULL, "defconfig_menu");
    char *read_line = (char *)lm_malloc(MAX_PER_LINE_LENGTH);
    memset(read_line, 0, MAX_PER_LINE_LENGTH);

    char *read_scan = NULL;
    FILE *file_p = NULL;
    int line_count = 0;

    int ret = fopen_s(&file_p, path, "r"); // read only
    if (ret) {
        lm_parser_error(path, line_count, "No such file");
        return LM_ERR;
    }

    while (fgets(read_line, MAX_PER_LINE_LENGTH, file_p)) {

        line_count++;

        if (read_line[strlen(read_line) - 1] == '\n' 
           || read_line[strlen(read_line) - 1] == '\r') {
            read_line[strlen(read_line) - 1] = '\0';
        }

        if (lm_is_skip_line(read_line)) {
            continue;
        }

        if(lm_parser_defconfig_args_key(read_line) == LM_OK){
            continue;
        }

        char *macro_name = lm_str_delete_space(read_line);
        if (macro_name == NULL) {
            lm_parser_error(path, line_count, "syntax error");
            return LM_ERR;
        }

        macro_name = lm_parser_defconfig_get_macro_name(macro_name);
        if (macro_name == NULL) {
            lm_parser_error(path, line_count, "syntax error");
            return LM_ERR;
        }

        macro = lm_macro_new_and_add(defconfig_menu_root, macro_name);
        if (macro == NULL) {
            printf("macro new error\n");
            return LM_ERR;
        }

        lm_free(macro_name);

        read_scan = lm_parser_defconfig_get_macro_value(read_line);
        if (read_scan == NULL) {
            lm_parser_error(path, line_count, "syntax error");
            return LM_ERR;
        }

        if(lm_str_is_all_space(read_scan)) {
            lm_macro_value_add(macro, " ");
        }
        else {
            char *macro_value = lm_str_delete_space(read_scan);
            lm_macro_value_add(macro, macro_value);
            lm_free(macro_value);
        }
    }

    fclose(file_p); // close file
    return LM_OK;
}


static lm_macro_t *lm_parser_prompt_is_macro(lm_macro_t *macro, char *read_line)
{
    lm_macro_t *ret;
    if (read_line[0] != ' ') {
        if (lm_str_num_str_space(read_line) == 1) {
            char *name = lm_str_delete_space(read_line);
            ret = lm_macro_new_and_add(macro, name);

            lm_free(name);
            return ret;
        }
    }
    return NULL;
}


static char *lm_parser_prompr_process_var(char *read_line)
{
    char *rel_val = lm_malloc(MAX_PER_LINE_LENGTH);
    if(rel_val == NULL) {
        return NULL;
    }
    memset(rel_val, 0, MAX_PER_LINE_LENGTH);
    char *rel_p = rel_val;

    char *p = read_line;
    char var_name[MAX_MACRO_NAME];
    int i = 0;

    while(*p) {
        if(*p == '$' && *(p+1) == '(') {
            p+=2;
            while(*p) {
                if(*p != ')') {
                   var_name[i++] = *p;
                }
                else {
                    var_name[i] = '\0';

                    lm_macro_t * macro = lm_macro_search_by_name(menu_root, var_name);
                    if(macro != NULL) {
                        if(macro->value != NULL) {
                            strcat(rel_val, macro->value);
                            rel_p += strlen(macro->value);
                        }
                    }

                    i = 0;
                    break;
                }

                p++;
            }
        }
        else {
            *rel_p++ = *p;
        }

        p++;
    }

    if(i != 0) {
        return NULL;
    }
    
    *rel_p = '\0';
    return rel_val;
}


static char *lm_parser_prompt_is_include(char *read_line)
{
    char *tmp = NULL;

    if (read_line[0] != ' ') {
        if (lm_str_num_str_space(read_line) == 2) {
            if (lm_str_find_str_space(read_line, "include") == 0) {
                tmp = lm_str_get_quote(read_line);
                return lm_parser_prompr_process_var(tmp);
            }
        }
    }
    return NULL;
}


static lm_parser_err_e lm_parser_prompt_is_choice_number_array(lm_macro_t *macro, char *str)
{
    char *p_sc = str;
    int str_len = strlen(str);
    int colon = 0;

    while(*p_sc) {
        if(*p_sc == ':') {
            colon ++;
        }
        else if(!((*p_sc >= '0' && *p_sc <= '9') ||  *p_sc == '-')) {
            return LM_PARSER_NOT_MATCH;
        }

        p_sc ++ ;
    }

    if(colon != 2) {
        return LM_PARSER_NOT_MATCH;
    }

    int seq[3];
    char num[MAX_MACRO_NAME];
    int i = 0, index = 0;

    for(int k = 0; k < str_len; k ++) {
        if(*str == ':') {
            num[i++] = '\0';
            *str = '\0';

            seq[index] = lm_str_to_int(num);
            if(seq[index] == LM_INVALID_NUM) {
                return LM_PARSER_NOT_MATCH;
            }

            index++;
            i = 0;
        }
        else {
            num[i++] = *str;
        }

        str ++;
    }
    num[i++] = '\0';
    seq[index] = lm_str_to_int(num);
    if(seq[index++] == LM_INVALID_NUM) {
        return LM_PARSER_NOT_MATCH;
    }
    
    char elem[64];
    for(int i = seq[0]; i <= (seq[1]*seq[2]); i += seq[2]) {
        sprintf(elem, "%d", i);
        lm_macro_choice_add(macro, elem);
    }

    return LM_PARSER_OK;
}


static int lm_parser_prompt_choice_add_value(lm_macro_t *macro, char *value_str)
{
    char *p = value_str;
    int len = strlen(p) + 1;

    char stack[MAX_MACRO_NAME];
    char *stack_p = stack;
    memset(stack, 0, MAX_MACRO_NAME);
    bool flag = false;

    for (int i = 0; i < len; i++) {

        if(*p == '{' && *p != '\0') {
            flag = true;
            while(*p) {
                *stack_p++ = *p++;
                i ++;

                if(*p == '}' && *p != '\0') {
                    flag = false;
                    break;
                }
            }
        }

        if(*p == '\"' && *p != '\0') {
            flag = true;
            while(*p) {
                *stack_p++ = *p++;
                i ++;

                if(*p == '\"' && *p != '\0') {
                    flag = false;
                    break;
                }
            }
        }

        if(*p == '\'' && *p != '\0') {
            p ++;
            flag = true;
            while(*p) {
                *stack_p++ = *p++;
                i ++;

                if(*p == '\'' && *p != '\0') {
                    flag = false;
                    break;
                }
            }
        }

        if(*p != ' ' && *p != '\'') {
            if (*p != ',' && *p != '\0') {
                *stack_p++ = *p;
            }
            else {
                *stack_p = '\0';

                lm_macro_choice_add(macro, stack);
                stack_p = stack;
            }
        }

        p++;
    }

    if(flag) {
        return -1;
    }
    return 0;
}


static lm_parser_err_e lm_parser_prompt_is_choice(lm_macro_t *macro, char *read_line)
{
    char *p = read_line;

    if (!lm_str_head_is_four_space(read_line))
        return LM_PARSER_SYNTAX;

    if (lm_str_find_str_space(read_line, "choice") == 0 
         && lm_str_find_str_space(read_line, "=") == 1) {

        while (*p) {
            if (*p != '=')
                p++;
            else
                break;
        }

        char *value_str = NULL;
        lm_str_dupli_string(&value_str, ++p);

        lm_parser_err_e err_ret = lm_parser_prompt_is_choice_number_array(macro, value_str);
        if(err_ret == LM_PARSER_OK) {
            return LM_PARSER_OK;
        }

        if (value_str[0] == ',' || value_str[strlen(value_str) - 1] == ',') {
            return LM_PARSER_SYNTAX;
        }

        if(lm_parser_prompt_choice_add_value(macro, value_str)) {
            return LM_PARSER_SYNTAX;
        }

        lm_free(value_str);

        return LM_PARSER_OK;
    }

    return LM_PARSER_NOT_MATCH;
}


static lm_parser_err_e lm_parser_prompt_is_depend(lm_macro_t *macro, char *read_line)
{
    char *p = read_line;

    if (!lm_str_head_is_four_space(read_line))
        return LM_PARSER_SYNTAX;

    if (lm_str_find_str_space(read_line, "depend") == 0 
        && lm_str_find_str_space(read_line, "=") == 1) {

        while (*p) {
            if (*p != '=')
                p++;
            else
                break;
        }

        lm_macro_depend_add(macro, p+1);

        return LM_PARSER_OK;
    }

    return LM_PARSER_NOT_MATCH;
}


static lm_parser_err_e lm_parser_prompt_is_description(lm_macro_t *macro, char *read_line)
{
    char *p = read_line;

    if (!lm_str_head_is_four_space(read_line))
        return LM_PARSER_SYNTAX;

    if (lm_str_find_str_space(read_line, "description") == 0 
          && lm_str_find_str_space(read_line, "=") == 1) {

        while (*p) {
            if (*p != '=')
                p++;
            else
                break;
        }

        char *descp = lm_str_get_quote(p+1);
        if(descp == NULL)
            return LM_PARSER_SYNTAX;

        lm_macro_description_add(macro, descp);
        lm_free(descp);

    return LM_PARSER_OK;
    }

    return LM_PARSER_NOT_MATCH;
}


static lm_parser_err_e lm_parser_prompt_is_src(char *read_line)
{
    char *p = read_line;
    bool flag = false;
    if(p[0] == 's' && p[1] == 'r' && p[2] == 'c' && p[3] == '-' && p[4] == 'y') {
        p += 5;
        while(*p) {
            if(*p == '+' && *(p+1) == '=') {
                return LM_PARSER_OK;
            }
            else if(*p == ' ') {
                p ++;
            }
            else {
                return LM_PARSER_SYNTAX; 
            }
        }

        return LM_PARSER_SYNTAX; 
    }
    else if(p[0] == 's' && p[1] == 'r' && p[2] == 'c' && p[3] == '-' && p[4] == '$'
           && p[5] == '(' ) {
        p += 6;
        while(*p) {
            if(*(++p) == ')') {
                flag = true;
                break;
            }
        }
        p++;

        if(!flag) {
            return LM_PARSER_SYNTAX;
        }

        while(*p) {
            if(*p == '+' && *(p+1) == '=') {
                return LM_PARSER_OK;
            }
            else if(*p == ' ') {
                p ++;
            }
            else {
                return LM_PARSER_SYNTAX; 
            }
        }

        return LM_PARSER_SYNTAX; 
    }

    return LM_PARSER_NOT_MATCH;
}


static lm_parser_err_e lm_parser_prompt_is_obj(char *read_line)
{
    char *p = read_line;
    bool flag = false;
    if(p[0] == 'o' && p[1] == 'b' && p[2] == 'j' && p[3] == '-' && p[4] == 'y') {
        p += 5;
        while(*p) {
            if(*p == '+' && *(p+1) == '=') {
                return LM_PARSER_OK;
            }
            else if(*p == ' ') {
                p ++;
            }
            else {
                return LM_PARSER_SYNTAX; 
            }
        }

        return LM_PARSER_SYNTAX; 
    }
    else if(p[0] == 'o' && p[1] == 'b' && p[2] == 'j' && p[3] == '-' && p[4] == '$'
           && p[5] == '(' ) {
        p += 6;
        while(*p) {
            if(*(++p) == ')') {
                flag = true;
                break;
            }
        }
        p++;

        if(!flag) {
            return LM_PARSER_SYNTAX;
        }

        while(*p) {
            if(*p == '+' && *(p+1) == '=') {
                return LM_PARSER_OK;
            }
            else if(*p == ' ') {
                p ++;
            }
            else {
                return LM_PARSER_SYNTAX; 
            }
        }

        return LM_PARSER_SYNTAX; 
    }

    return LM_PARSER_NOT_MATCH;
}


static lm_parser_err_e lm_parser_prompt_is_args_key(char *key, char *read_line)
{
    char *p = read_line;
    int key_len = strlen(key);

    for(int i = 0; i < key_len; i ++) {
        if(p[i] == key[i]) {
            continue;
        }
        else {
            return LM_PARSER_NOT_MATCH;
        }
    }

    

    p += (key_len);
    
    while(*p) {
        if(*p == '+' && *(p+1) == '=') {
            return LM_PARSER_OK;
        }
        else if(*p == ' ') {
            p ++;
        }
        else {
            return LM_PARSER_SYNTAX; 
        }
    }

    return LM_PARSER_SYNTAX; 
}


static lm_parser_err_e lm_parser_prompt_is_src_or_obj(char *read_line)
{
    if(lm_parser_prompt_is_src(read_line) != LM_PARSER_OK) {
        return lm_parser_prompt_is_obj(read_line);
    }
    else {
        return LM_PARSER_OK;
    }
}


static void lm_parser_macro_set_value(lm_macro_t *macro)
{
    if(macro == NULL) {
        return;
    }

    int value = lm_parser_get_macro_depend_value(macro, false);
    if(value == 1) {
        char *value_p = lm_parser_get_macro_value_from_menu(defconfig_menu_root, macro);
        if(value_p == NULL) {
            char *first_value = lm_macro_choice_get_first(macro);
            if(first_value)
                lm_macro_value_add(macro, first_value);
        }
        else {
            lm_macro_value_add(macro, value_p);
        }
    }
    else if(strcmp(macro->value, "y") == 0){
        lm_macro_value_add(macro, "n");
    }
    else if(strcmp(macro->value, "n") != 0) {
        lm_macro_value_add(macro, " ");
    }
}


static int lm_parser_macro_set_prompt(lm_macro_t *macro, char *read_line)
{
    lm_parser_err_e prompt_ret = LM_PARSER_NOT_MATCH;

    prompt_ret = lm_parser_prompt_is_choice(macro, read_line);
    if(prompt_ret == LM_PARSER_SYNTAX) {
        return LM_ERR;
    }
    else if(prompt_ret == LM_PARSER_OK) {
        if(macro->value == NULL) {
            lm_parser_macro_set_value(macro);
        }
        return LM_OK;
    }

    prompt_ret = lm_parser_prompt_is_depend(macro, read_line);
    if(prompt_ret == LM_PARSER_SYNTAX) {
        return LM_ERR;
    }
    else if(prompt_ret == LM_PARSER_OK) {
        return LM_OK;
    }

    prompt_ret = lm_parser_prompt_is_description(macro, read_line);
    if(prompt_ret == LM_PARSER_SYNTAX) {
        return LM_ERR;
    }
    else if(prompt_ret == LM_PARSER_OK) {
        return LM_OK;
    }

    return LM_ERR;
}


static bool lm_parser_forelook_is_macro(char *read_line)
{
    char *sub_file = lm_parser_prompt_is_include(read_line);
    if(sub_file != NULL) {
        return false;
    }

    lm_parser_err_e prompt_err = lm_parser_prompt_is_src_or_obj(read_line);
    if(prompt_err == LM_PARSER_OK) {
        return false;
    }

    prompt_err = lm_parser_prompt_is_args_key("path", read_line);
    if(prompt_err == LM_PARSER_OK) {
        return false;
    }

    prompt_err = lm_parser_prompt_is_args_key("define", read_line);
    if(prompt_err == LM_PARSER_OK) {
        return false;
    }

    prompt_err = lm_parser_prompt_is_args_key("asm", read_line);
    if(prompt_err == LM_PARSER_OK) {
        return false;
    }

    prompt_err = lm_parser_prompt_is_args_key("lds", read_line);
    if(prompt_err == LM_PARSER_OK) {
        return false;
    }

    return true;
}

static lm_parser_err_e lm_parser_forelook_macro_exist(const char *base_path,const char *path)
{
    FILE *file_p = NULL;
    char *read_line = (char *)lm_malloc(MAX_PER_LINE_LENGTH);

    char full_path[MAX_FILE_PATH];
    if(base_path != NULL) {
        sprintf(full_path, "%s/%s", base_path, path);
    }
    else {
        strcpy(full_path, path);
    }

    int ret = fopen_s(&file_p, full_path, "r"); // read only
    if (ret) {
        return LM_PARSER_SYNTAX;
    }

    while (fgets(read_line, MAX_PER_LINE_LENGTH, file_p)) {
        if (read_line[strlen(read_line) - 1] == '\n' 
            || read_line[strlen(read_line) - 1] == '\r') {

            read_line[strlen(read_line) - 1] = '\0';
        }

        if (lm_is_skip_line(read_line)) {
            continue;
        }

        if(lm_parser_forelook_is_macro(read_line)) {
            goto found;
        }
    }
    
    fclose(file_p); // close file
    lm_free(read_line);
    return LM_PARSER_NOT_MATCH;
    
found:
    fclose(file_p); // close file
    lm_free(read_line);
    return LM_PARSER_OK;
}


int lm_parser_project_file(lm_macro_t *menu, const char *base_path, const char *path)
{
    char *read_line = (char *)lm_malloc(MAX_PER_LINE_LENGTH);
    memset(read_line, 0, MAX_PER_LINE_LENGTH);

    int line_count = 0;
    bool macro_flag = false;
    lm_macro_t *macro = NULL;
    
    FILE *file_p = NULL;

    char full_path[MAX_FILE_PATH];
    if(base_path != NULL) {
        sprintf(full_path, "%s/%s", base_path, path);
    }
    else {
        strcpy(full_path, path);
    }

    int ret = fopen_s(&file_p, full_path, "r"); // read only
    if (ret) {
        lm_parser_error(path, line_count, "No such file");
        return LM_ERR;
    }

    char new_base_path[MAX_FILE_PATH];
    strcpy(new_base_path, full_path);

    char *last_dir = strrchr(new_base_path, '/');
    if(last_dir) {
        *last_dir = '\0';
    }
    else {
        strcpy(new_base_path, ".");
    }

    while (fgets(read_line, MAX_PER_LINE_LENGTH, file_p)) {

        line_count++;

        if (read_line[strlen(read_line) - 1] == '\n' 
            || read_line[strlen(read_line) - 1] == '\r') {

            read_line[strlen(read_line) - 1] = '\0';
        }

        if (lm_is_skip_line(read_line)) {
            macro_flag = false;

            if(macro != NULL) {
                if(macro->choice.count == 0) {
                    lm_parser_error(path, line_count, "missing 'choice' attribute");
                    goto error;
                }
            }
            lm_parser_macro_set_value(macro);

            macro = NULL;
            continue;
        }

        char *sub_file = lm_parser_prompt_is_include(read_line);
        if(sub_file != NULL) {

            lm_parser_err_e forelook = lm_parser_forelook_macro_exist(new_base_path, sub_file);
            if(forelook == LM_PARSER_NOT_MATCH) {
                continue;
            }
            else if(forelook == LM_PARSER_SYNTAX) {
                fclose(file_p); // close file
                lm_parser_error(path, line_count, "No such file");
                return LM_ERR;
            }

            lm_macro_t *sub_macro = lm_macro_add_menu(menu, sub_file);
            if(sub_macro == NULL) {
                fclose(file_p); // close file
                return LM_ERR;
            }
            //set parent
            lm_macro_parent_set(sub_macro, macro_parent);
            macro_parent = menu;

            int sub_ret = lm_parser_project_file(sub_macro, new_base_path, sub_file);
            if(sub_ret != LM_OK) {
                return LM_ERR;
            }
            continue;
        }

        lm_parser_err_e prompt_err = lm_parser_prompt_is_src_or_obj(read_line);
        if(prompt_err == LM_PARSER_OK) {
            continue;
        }
        else if(prompt_err == LM_PARSER_SYNTAX) {
            goto error;
        }

        prompt_err = lm_parser_prompt_is_args_key("path", read_line);
        if(prompt_err == LM_PARSER_OK) {
            continue;
        }
        else if(prompt_err == LM_PARSER_SYNTAX) {
            goto error;
        }

        prompt_err = lm_parser_prompt_is_args_key("define", read_line);
        if(prompt_err == LM_PARSER_OK) {
            continue;
        }
        else if(prompt_err == LM_PARSER_SYNTAX) {
            goto error;
        }
    
        prompt_err = lm_parser_prompt_is_args_key("asm", read_line);
        if(prompt_err == LM_PARSER_OK) {
            continue;
        }
        else if(prompt_err == LM_PARSER_SYNTAX) {
            goto error;
        }

        prompt_err = lm_parser_prompt_is_args_key("lds", read_line);
        if(prompt_err == LM_PARSER_OK) {
            continue;
        }
        else if(prompt_err == LM_PARSER_SYNTAX) {
            goto error;
        }

        lm_macro_t *tmp_macro = lm_parser_prompt_is_macro(menu, read_line);
        if(tmp_macro == NULL) {
            if(!macro_flag) {
                lm_parser_error(path, line_count, "invalid syntax");
                fclose(file_p); // close file
                return LM_ERR;
            }
            else {
                if(lm_parser_macro_set_prompt(macro, read_line) == LM_ERR) {
                    goto error;
                }
            }
        }
        else {
            macro = tmp_macro;
            macro_flag = true;
            lm_macro_parent_set(macro, macro_parent);
        }
    }

    lm_parser_macro_set_value(macro);

    fclose(file_p); // close file
    return LM_OK;

error:
    lm_parser_error(path, line_count, "invalid syntax");
    fclose(file_p); // close file
    return LM_ERR;
}


static void lm_parser_src_obj_add_wildcard(const char *wildcard, const char *path, lm_array_t *array)
{
    DIR *dir;
    struct dirent *entry;
    char file_name[MAX_FILE_PATH];
    char file_extension[3];

    dir = opendir(".");
    if (dir == NULL) {
        LM_LOG_ERROR("opening %s directory", path);
    }

    strncpy(file_extension, wildcard + 1, 2);

    while ((entry = readdir(dir)) != NULL) {
        size_t len = strlen(entry->d_name);

        if (len >= 2 && strcmp(entry->d_name + len - 2, ".c") == 0) {
            sprintf(file_name, "%s/%.*s%s", path, (int)(len - 2), entry->d_name, file_extension);
            lm_array_add_string(array, file_name);
        }
    }

    closedir(dir);
}


void lm_parser_src_obj_add(lm_macro_t *menu, const char *path, char *readline)
{
    lm_array_t *array = NULL;
    char macro_name[MAX_MACRO_NAME];
    char file_name[MAX_FILE_PATH];
    char *p = readline;
    int i = 0;
    bool flag = false;

    if(lm_parser_prompt_is_src(readline) == LM_PARSER_OK) {
        array = &src_list;
    }
    else if(lm_parser_prompt_is_obj(readline) == LM_PARSER_OK) {
        array = &obj_list;
    }

    while(*p++) {
        if(*p == 'y') {
            flag = true;
        }
        else if(*p == '$' && *(p+1) == '(') {
            p += 1;
            while(*p++) {
                if(*p == ')') {
                    macro_name[i++] = '\0';
                    break;
                }
                macro_name[i++] = *p;
            }
        }

        if(*p == '+' && *(p+1) == '=') {
            p += 2;
            break;
        }
    }

    if(!flag) {
        lm_macro_t * macro = lm_macro_search_by_name(menu, macro_name);
        if(macro != NULL) {
            if(strcmp(macro->value, "y") != 0) {
                return;
            }
        }
        else {
            return;
        }
    }

    int num = lm_str_num_str_space(p);
    char *pick = NULL;

    for(int i = 0; i < num; i++) {
        pick = lm_str_pick_str(p, i);
        if(pick && array) {
            if((strcmp(pick, "*.c") == 0) || (strcmp(pick, "*.o") == 0)) {
                lm_parser_src_obj_add_wildcard(pick, path, array);
                lm_free(pick);
            }
            else {
                if(strcmp(path, ".") == 0)
                    sprintf(file_name, "%s", pick);
                else
                    sprintf(file_name, "%s/%s", path, pick);

                lm_array_add_string(array, file_name);
                lm_free(pick);
            }
        }
    }
}


void lm_parser_path_add(lm_macro_t *menu, const char *path, char *readline)
{
    lm_array_t *array = &path_list;
    char file_name[MAX_FILE_PATH];
    char *p = readline;

    while(*p++) {
        if(*p == '+' && *(p+1) == '=') {
            p += 2;
            break;
        }
    }

    int num = lm_str_num_str_space(p);
    char *pick = NULL;

    for(int i = 0; i < num; i++) {
        pick = lm_str_pick_str(p, i);
        if(pick && array) {
            if(strcmp(path, ".") == 0) {
                if(strcmp(pick, ".") == 0 || strcmp(pick, "./") == 0) {
                    sprintf(file_name, "-I./");
                }
                else {
                    sprintf(file_name, "-I%s", pick);
                }
            }
            else {
                if(strcmp(pick, ".") == 0 || strcmp(pick, "./") == 0) {
                    sprintf(file_name, "-I%s", path);
                }
                else {
                    sprintf(file_name, "-I%s/%s", path, pick);
                }
            }

            lm_array_add_string(array, file_name);
            lm_free(pick);
        }
    }
}


void lm_parser_define_add(lm_macro_t *menu, char *readline)
{
    lm_array_t *array = &define_list;
    char g_define[MAX_FILE_PATH];
    char *p = readline;

    while(*p++) {
        if(*p == '+' && *(p+1) == '=') {
            p += 2;
            break;
        }
    }

    int num = lm_str_num_str_space(p);
    char *pick = NULL;

    for(int i = 0; i < num; i++) {
        pick = lm_str_pick_str(p, i);
        if(pick && array) {
            sprintf(g_define, "-D%s", pick);
            lm_array_add_string(array, g_define);
            lm_free(pick);
        }
    }
}


void lm_parser_asm_add(lm_macro_t *menu, const char *path, char *readline)
{
    lm_array_t *array = &asm_list;
    char file_name[MAX_FILE_PATH];
    char *p = readline;

    while(*p++) {
        if(*p == '+' && *(p+1) == '=') {
            p += 2;
            break;
        }
    }

    int num = lm_str_num_str_space(p);
    char *pick = NULL;

    for(int i = 0; i < num; i++) {
        pick = lm_str_pick_str(p, i);
        if(pick && array) {
            if(strcmp(path, ".") == 0)
                sprintf(file_name, "%s", pick);
            else
                sprintf(file_name, "%s/%s", path, pick);

            lm_array_add_string(array, file_name);
            lm_free(pick);
        }
    }
}


void lm_parser_lds_add(lm_macro_t *menu, const char *path, char *readline)
{
    lm_array_t *array = &lds_list;
    char file_name[MAX_FILE_PATH];
    char *p = readline;

    while(*p++) {
        if(*p == '+' && *(p+1) == '=') {
            p += 2;
            break;
        }
    }

    int num = lm_str_num_str_space(p);
    char *pick = NULL;

    for(int i = 0; i < num; i++) {
        pick = lm_str_pick_str(p, i);
        if(pick && array) {
            if(strcmp(path, ".") == 0)
                sprintf(file_name, "%s", pick);
            else
                sprintf(file_name, "%s/%s", path, pick);

            lm_array_add_string(array, file_name);
            lm_free(pick);
        }
    }
}



int lm_parser_args_list(lm_macro_t *menu, const char *base_path, const char *path)
{
    char *read_line = (char *)lm_malloc(MAX_PER_LINE_LENGTH);
    memset(read_line, 0, MAX_PER_LINE_LENGTH);

    int line_count = 0;
    
    FILE *file_p = NULL;

    char full_path[MAX_FILE_PATH];
    if(base_path != NULL && strcmp(base_path, ".") != 0)  {
        sprintf(full_path, "%s/%s", base_path, path);
    }
    else {
        strcpy(full_path, path);
    }

    int ret = fopen_s(&file_p, full_path, "r"); // read only
    if (ret) {
        lm_parser_error(path, line_count, "No such file");
        return LM_ERR;
    }

    char new_base_path[MAX_FILE_PATH];
    strcpy(new_base_path, full_path);
    char *last_dir = strrchr(new_base_path, '/');
    if(last_dir) {
        *last_dir = '\0';
    }
    else {
        strcpy(new_base_path, ".");
    }

    while (fgets(read_line, MAX_PER_LINE_LENGTH, file_p)) {

        line_count++;

        if (read_line[strlen(read_line) - 1] == '\n' 
            || read_line[strlen(read_line) - 1] == '\r') {

            read_line[strlen(read_line) - 1] = '\0';
        }

        if (lm_is_skip_line(read_line)) {
            continue;
        }

        if(lm_parser_prompt_is_src_or_obj(read_line) == LM_PARSER_OK) {
            lm_parser_src_obj_add(menu, new_base_path, read_line);
            continue;
        }

        if(lm_parser_prompt_is_args_key("path", read_line) == LM_PARSER_OK) {
            lm_parser_path_add(menu, new_base_path, read_line);
            continue;
        }
    
        if(lm_parser_prompt_is_args_key("define", read_line) == LM_PARSER_OK) {
            lm_parser_define_add(menu, read_line);
            continue;
        }

        if(lm_parser_prompt_is_args_key("asm", read_line) == LM_PARSER_OK) {
            lm_parser_asm_add(menu, new_base_path, read_line);
            continue;
        }
    
        if(lm_parser_prompt_is_args_key("lds", read_line) == LM_PARSER_OK) {
            lm_parser_lds_add(menu, new_base_path, read_line);
            continue;
        }

        char *sub_file = lm_parser_prompt_is_include(read_line);
        if(sub_file != NULL) {
            int sub_ret = lm_parser_args_list(menu, new_base_path, sub_file);
            if(sub_ret != LM_OK) {
                goto error;
            }
            continue;
        }
    }
    fclose(file_p); // close file
    return LM_OK;

error:
    lm_parser_error(path, line_count, "invalid syntax");
    fclose(file_p); // close file
    return LM_ERR;
}


void lm_parser_rescan_menu_macro(lm_macro_t *menu)
{
    int ret;
    lm_list_node_t *head = &menu->head;
    lm_list_node_t *node;
    lm_macro_t *macro = NULL;

    lm_list_for_each(node, head) {

        macro = container_of(node, lm_macro_t, node);
        if(macro == NULL) {
            return;
        }

        ret = lm_parser_get_macro_depend_value(macro, true);
        if(ret == 0) {
            if(strcmp(macro->value, "y") == 0) {
                lm_macro_value_add(macro, "n");
            }
            else if(strcmp(macro->value, "n") != 0){
                lm_macro_value_add(macro, " ");
            }
        }

        if(macro->menu) {

            lm_parser_rescan_menu_macro(macro);
        }
    }
}


int lm_parser_header_value_write_file(FILE* file, const char* file_path, lm_macro_t *menu)
{
    lm_list_node_t *head = &menu->head;
    lm_list_node_t *node;
    lm_macro_t *macro = NULL;

    lm_list_for_each(node, head) {

        macro = container_of(node, lm_macro_t, node);
        if(macro == NULL) {
            return LM_ERR;
        }

        if(macro->value != NULL) {

            if(strcmp(macro->value, " ") == 0){
                if(macro->description) {
                    fprintf(file, "//%s is not set\n", macro->name);
                }
            }
            else {
                if(macro->description) {
                    fprintf(file, "//%s\n", macro->description);
                }
            }

            if(strcmp(macro->value, "y") == 0) {
                if (fprintf(file, "#define    %-30s     1\n", macro->name) < 0) {
                    printf("Failed to write to the %s\n", file_path);
                    return LM_ERR;
                }
            }
            else if(strcmp(macro->value, "n") == 0) {
                if (fprintf(file, "#define    %-30s     0\n", macro->name) < 0) {
                    printf("Failed to write to the %s\n", file_path);
                    return LM_ERR;
                }
            }
            else if(strcmp(macro->value, " ") != 0){
                if (fprintf(file, "#define    %-30s     %-s\n", macro->name, macro->value) < 0) {
                    printf("Failed to write to the %s\n", file_path);
                    return LM_ERR;
                }
            }
        }
    
        if(macro->menu) {
            lm_parser_header_value_write_file(file, file_path, macro);
        }
    }
    return LM_OK;
}


int lm_parser_all_value_write_file(FILE* file, const char* file_path, lm_macro_t *menu)
{
    lm_list_node_t *head = &menu->head;
    lm_list_node_t *node;
    lm_macro_t *macro = NULL;

    lm_list_for_each(node, head) {

        macro = container_of(node, lm_macro_t, node);
        if(macro == NULL) {
            return LM_ERR;
        }

        if(macro->value != NULL) {
            if(macro->description) {
                if (fprintf(file, "# %s\n", macro->description) < 0) {
                    printf("Failed to write to the %s\n", file_path);
                    return LM_ERR;
                }
            }

            if (fprintf(file, "%s = %s\n", macro->name, macro->value) < 0) {
                printf("Failed to write to the %s\n", file_path);
                return LM_ERR;
            }
        }
    
        if(macro->menu) {
            lm_parser_all_value_write_file(file, file_path, macro);
        }
    }
    return LM_OK;
}


int lm_parser_gen_header_file(lm_macro_t *menu, const char* file_path)
{
    FILE* file = fopen(file_path, "w");
    if (file == NULL) {
        printf("Failed to open or create the %s\n", file_path);
        return LM_ERR;
    }

    fprintf(file, "//****************************************************************\n");
    fprintf(file, "//* lite-manager                                                 *\n");
    fprintf(file, "//* NOTE: do not edit this file as it is automatically generated *\n");
    fprintf(file, "//****************************************************************\n\n");
    fprintf(file, "#ifndef  __CONFIG_H__\n#define  __CONFIG_H__\n\n\n");

    lm_parser_header_value_write_file(file, file_path, menu);

    fprintf(file, "\n\n#endif  //!__CONFIG_H__\n");
    
    fclose(file);
    return LM_OK;
}


int lm_parser_gen_config_file(lm_macro_t *menu, const char* file_path)
{
    FILE* file = fopen(file_path, "w");
    if (file == NULL) {
        printf("Failed to open or create the %s\n", file_path);
        return LM_ERR;
    }

    fprintf(file, "#****************************************************************\n");
    fprintf(file, "#* lite-manager                                                 *\n");
    fprintf(file, "#* NOTE: do not edit this file as it is automatically generated *\n");
    fprintf(file, "#****************************************************************\n\n");

    lm_parser_all_value_write_file(file, file_path, menu);

    fclose(file);
    return LM_OK;
}


lm_macro_t *lm_parser_get_menu_root(void)
{
    return menu_root;
}


int lm_parser_args_write_file(lm_macro_t *menu, const char *conf_path, const char* file_path)
{
    lm_parser_args_list(menu, NULL, conf_path);

    FILE* file = fopen(file_path, "a+");
    if (file == NULL) {
        printf("Failed to open or create the %s\n", file_path);
        return LM_ERR;
    }

    if(lds_list.count) {
        fprintf(file, "\n# link script source file list\n");
        fprintf(file, "%s := ", lds_key_flag);
        lm_array_print(file, &lds_list);
    }

    if(asm_list.count) {
        fprintf(file, "# asm source file list\n");
        fprintf(file, "%s := ", asm_key_flag);
        lm_array_print(file, &asm_list);
    }

    if(define_list.count) {
        fprintf(file, "# C or C++ global define list\n");
        fprintf(file, "%s := ", define_key_flag);
        lm_array_print(file, &define_list);
    }

    if(path_list.count) {
        fprintf(file, "# C or C++ include path list\n");
        fprintf(file, "%s := ", path_key_flag);
        lm_array_print(file, &path_list);
    }

    if(src_list.count) {
        fprintf(file, "# C or C++ source file list\n");
        fprintf(file, "%s := ", src_key_flag);
        lm_array_print(file, &src_list);
    }

    if(obj_list.count) {
        fprintf(file, "# C or C++ object file list\n");
        fprintf(file, "%s := ", obj_key_flag);
        lm_array_print(file, &obj_list);
    }

    fclose(file);
    return LM_OK;
}


int lm_parser_main(const char* defconf_path, const char* conf_path)
{
    int ret;

    //initialize src and obj list head
    lm_list_init(&path_list.head);
    lm_list_init(&src_list.head);
    lm_list_init(&obj_list.head);
    lm_list_init(&define_list.head);
    lm_list_init(&asm_list.head);
    lm_list_init(&lds_list.head);

    //parser defconfig file 
    ret = lm_parser_defconfig_file(defconf_path);
    if(ret == LM_ERR) {
        return ret;
    }

    //add menu root
    menu_root = lm_macro_add_menu(NULL, "menu_root");
    macro_parent = menu_root;
    //parser config file
    ret = lm_parser_project_file(menu_root, NULL, conf_path);

    return ret;
}
